// SPDX-FileCopyrightText: Copyright 2024 citrus Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <set>
#include "common/settings_input.h"
#include "input_common/drivers/android.h"
#include "binder.h"

using KeyCode = aidl::com::fanwj::citrus::aidl::IFrontend::KeyCode;

namespace InputCommon {

    static const std::string sony_vid{"054c"};
    static const std::string nintendo_vid{"057e"};
    static const std::string razer_vid{"1532"};
    static const std::string redmagic_vid{"3537"};
    static const std::string backbone_labs_vid{"358a"};
    static const std::string xbox_vid{"045e"};
    static const std::vector<std::string> flipped_ab_vids{sony_vid,     nintendo_vid,      razer_vid,
                                                   redmagic_vid, backbone_labs_vid, xbox_vid};
    static const std::vector<std::string> flipped_xy_vids{sony_vid, razer_vid, redmagic_vid,
                                                   backbone_labs_vid, xbox_vid};

    static inline PadIdentifier GetIdentifier(auto& guid, size_t port) {
        return {
                .guid = Common::UUID(guid),
                .port = port,
                .pad = 0,
        };
    }

    static std::set<int> GetAxesOfController(const std::string& guid) {
        std::vector<int> axes;
        Binder::getInstanceFrontend()->NativeInput_getAxesOfController(guid, &axes);
        return std::set<int>{axes.begin(), axes.end()};
    }

    static std::set<int> GetKeysOfController(const std::string& guid) {
        std::vector<int> res;
        Binder::getInstanceFrontend()->NativeInput_getKeysOfController(guid, &res);
        return std::set<int>{res.begin(), res.end()};
    }

    Android::Android(const std::string& input_engine_) : InputEngine(input_engine_) {
    }

    Android::~Android() = default;

    bool Android::IsVibrationEnabled(const PadIdentifier& identifier) {
        u128 value = identifier.guid.AsU128();
        return !(value[0] || value[1]);
    }

    void Android::SetButtonState(const std::string& guid, size_t port, int button_id, bool value) {
        const auto identifier = GetIdentifier(guid, port);
        SetButton(identifier, button_id, value);
    }

    void Android::SetAxisPosition(const std::string& guid, size_t port, int axis_id, float value) {
        const auto identifier = GetIdentifier(guid, port);
        SetAxis(identifier, axis_id, value);
    }

    void Android::SetMotionState(const std::string& guid, size_t port, u64 delta_timestamp, float gyro_x,
                                 float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z) {
        const auto identifier = GetIdentifier(guid, port);
        const BasicMotion motion_data{
                .gyro_x = gyro_x,
                .gyro_y = gyro_y,
                .gyro_z = gyro_z,
                .accel_x = accel_x,
                .accel_y = accel_y,
                .accel_z = accel_z,
                .delta_timestamp = delta_timestamp,
        };
        SetMotion(identifier, 0, motion_data);
    }

    std::vector<Common::ParamPackage> Android::GetInputDevices() const {
        std::vector<Common::ParamPackage> devices;
        std::vector<std::string> controllers;
        Binder::getInstanceFrontend()->NativeInput_getPropertyOfControllers(&controllers);
        assert(0 == controllers.size() % 3);
        auto engine = GetEngineName();
        for (size_t i = 0; i < controllers.size(); i += 3) {
            auto& name = controllers[i];
            auto& guid = controllers[i + 1];
            auto& port = controllers[i + 2];
            devices.emplace_back(Common::ParamPackage{
                    {"engine", engine},
                    {"display", name + " " + port},
                    {"guid", guid},
                    {"port", port},
            });
        }
        return devices;
    }

    Common::ParamPackage Android::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
                                                             int axis_y) const {
        Common::ParamPackage params;
        params.Set("engine", GetEngineName());
        params.Set("port", static_cast<int>(identifier.port));
        params.Set("guid", identifier.guid.RawString());
        params.Set("axis_x", axis_x);
        params.Set("axis_y", axis_y);
        params.Set("offset_x", 0);
        params.Set("offset_y", 0);
        params.Set("invert_x", "+");

        // Invert Y-Axis by default
        params.Set("invert_y", "-");
        return params;
    }

    Common::ParamPackage Android::BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis,
                                                                   bool invert) const {
        Common::ParamPackage params{};
        params.Set("engine", GetEngineName());
        params.Set("port", static_cast<int>(identifier.port));
        params.Set("guid", identifier.guid.RawString());
        params.Set("axis", axis);
        params.Set("threshold", "0.5");
        params.Set("invert", invert ? "-" : "+");
        return params;
    }

    Common::ParamPackage Android::BuildButtonParamPackageForButton(PadIdentifier identifier,
                                                                   s32 button) const {
        Common::ParamPackage params{};
        params.Set("engine", GetEngineName());
        params.Set("port", static_cast<int>(identifier.port));
        params.Set("guid", identifier.guid.RawString());
        params.Set("button", button);
        return params;
    }

    bool Android::MatchVID(Common::UUID device, const std::vector<std::string>& vids) const {
        for (size_t i = 0; i < vids.size(); ++i) {
            auto fucker = device.RawString();
            if (fucker.find(vids[i]) != std::string::npos) {
                return true;
            }
        }
        return false;
    }

    AnalogMapping Android::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
        if (!params.Has("guid") || !params.Has("port")) {
            return {};
        }
        auto guid = params.Get("guid", "");
        auto identifier =
                GetIdentifier(guid, static_cast<size_t>(params.Get("port", 0)));

        auto axes = GetAxesOfController(guid);
        if (axes.size() == 0) {
            return {};
        }

        AnalogMapping mapping = {};
        if (axes.find((int)KeyCode::AXIS_X) != axes.end() && axes.find((int)KeyCode::AXIS_Y) != axes.end()) {
            mapping.insert_or_assign(Settings::NativeAnalog::LStick,
                                     BuildParamPackageForAnalog(identifier, (int)KeyCode::AXIS_X, (int)KeyCode::AXIS_Y));
        }

        if (axes.find((int)KeyCode::AXIS_RX) != axes.end() && axes.find((int)KeyCode::AXIS_RY) != axes.end()) {
            mapping.insert_or_assign(Settings::NativeAnalog::RStick,
                                     BuildParamPackageForAnalog(identifier, (int)KeyCode::AXIS_RX, (int)KeyCode::AXIS_RY));
        } else if (axes.find((int)KeyCode::AXIS_Z) != axes.end() && axes.find((int)KeyCode::AXIS_RZ) != axes.end()) {
            mapping.insert_or_assign(Settings::NativeAnalog::RStick,
                                     BuildParamPackageForAnalog(identifier, (int)KeyCode::AXIS_Z, (int)KeyCode::AXIS_RZ));
        }
        return mapping;
    }

    ButtonMapping Android::GetButtonMappingForDevice(const Common::ParamPackage& params) {
        if (!params.Has("guid") || !params.Has("port")) {
            return {};
        }
        auto guid = params.Get("guid", "");
        auto identifier = GetIdentifier(guid, static_cast<size_t>(params.Get("port", 0)));
        auto available_keys = GetKeysOfController(guid);
        // Some devices use axes instead of buttons for certain controls so we need all the axes here
        auto axes = GetAxesOfController(guid);

        ButtonMapping mapping = {};
        if (axes.find((int)KeyCode::AXIS_HAT_X) != axes.end() && axes.find((int)KeyCode::AXIS_HAT_Y) != axes.end()) {
            mapping.insert_or_assign(Settings::NativeButton::DUp,
                                     BuildAnalogParamPackageForButton(identifier, (int)KeyCode::AXIS_HAT_Y, true));
            mapping.insert_or_assign(Settings::NativeButton::DDown,
                                     BuildAnalogParamPackageForButton(identifier, (int)KeyCode::AXIS_HAT_Y, false));
            mapping.insert_or_assign(Settings::NativeButton::DLeft,
                                     BuildAnalogParamPackageForButton(identifier, (int)KeyCode::AXIS_HAT_X, true));
            mapping.insert_or_assign(Settings::NativeButton::DRight,
                                     BuildAnalogParamPackageForButton(identifier, (int)KeyCode::AXIS_HAT_X, false));
        } else if (available_keys.find((int)KeyCode::KEYCODE_DPAD_UP) != available_keys.end() &&
                   available_keys.find((int)KeyCode::KEYCODE_DPAD_DOWN) != available_keys.end() &&
                   available_keys.find((int)KeyCode::KEYCODE_DPAD_LEFT) != available_keys.end() &&
                   available_keys.find((int)KeyCode::KEYCODE_DPAD_RIGHT) != available_keys.end()) {
            mapping.insert_or_assign(Settings::NativeButton::DUp,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_DPAD_UP));
            mapping.insert_or_assign(Settings::NativeButton::DDown,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_DPAD_DOWN));
            mapping.insert_or_assign(Settings::NativeButton::DLeft,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_DPAD_LEFT));
            mapping.insert_or_assign(Settings::NativeButton::DRight,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_DPAD_RIGHT));
        }

        if (axes.find((int)KeyCode::AXIS_LTRIGGER) != axes.end()) {
            mapping.insert_or_assign(Settings::NativeButton::ZL, BuildAnalogParamPackageForButton(
                    identifier, (int)KeyCode::AXIS_LTRIGGER, false));
        } else if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_L2) != available_keys.end()) {
            mapping.insert_or_assign(Settings::NativeButton::ZL,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_L2));
        }

        if (axes.find((int)KeyCode::AXIS_RTRIGGER) != axes.end()) {
            mapping.insert_or_assign(Settings::NativeButton::ZR, BuildAnalogParamPackageForButton(
                    identifier, (int)KeyCode::AXIS_RTRIGGER, false));
        } else if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_R2) != available_keys.end()) {
            mapping.insert_or_assign(Settings::NativeButton::ZR,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_R2));
        }

        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_A) != available_keys.end()) {
            if (MatchVID(identifier.guid, flipped_ab_vids)) {
                mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_A));
            } else {
                mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_A));
            }
        }
        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_B) != available_keys.end()) {
            if (MatchVID(identifier.guid, flipped_ab_vids)) {
                mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_B));
            } else {
                mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_B));
            }
        }
        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_X) != available_keys.end()) {
            if (MatchVID(identifier.guid, flipped_xy_vids)) {
                mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_X));
            } else {
                mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_X));
            }
        }
        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_Y) != available_keys.end()) {
            if (MatchVID(identifier.guid, flipped_xy_vids)) {
                mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_Y));
            } else {
                mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton(
                        identifier, (int)KeyCode::KEYCODE_BUTTON_Y));
            }
        }

        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_L1) != available_keys.end()) {
            mapping.insert_or_assign(Settings::NativeButton::L,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_L1));
        }
        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_R1) != available_keys.end()) {
            mapping.insert_or_assign(Settings::NativeButton::R,
                                     BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_R1));
        }

        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_THUMBL) != available_keys.end()) {
            mapping.insert_or_assign(
                    Settings::NativeButton::LStick,
                    BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_THUMBL));
        }
        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_THUMBR) != available_keys.end()) {
            mapping.insert_or_assign(
                    Settings::NativeButton::RStick,
                    BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_THUMBR));
        }

        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_START) != available_keys.end()) {
            mapping.insert_or_assign(
                    Settings::NativeButton::Plus,
                    BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_START));
        }
        if (available_keys.find((int)KeyCode::KEYCODE_BUTTON_SELECT) != available_keys.end()) {
            mapping.insert_or_assign(
                    Settings::NativeButton::Minus,
                    BuildButtonParamPackageForButton(identifier, (int)KeyCode::KEYCODE_BUTTON_SELECT));
        }

        return mapping;
    }

    Common::Input::ButtonNames Android::GetUIName(
            [[maybe_unused]] const Common::ParamPackage& params) const {
        return Common::Input::ButtonNames::Value;
    }

} // namespace InputCommon
